#include "persistence.h"

#include "../documents/idocument.h"
#include "../utils/mimetypemanager.h"
#include "ipersistenceassistant.h"
#include "ipersistentdocument.h"
#include "istorage.h"

#include <QtCore/QUrl>

using namespace core;

Persistence* persistence = 0;

const QString Persistence::WILD_CARD = "*";

typedef QHash<QString, IPersistenceAssistant* > StorageNativeAssistantMapping;
typedef QHash<QString, QList<IPersistenceAssistant* > >
    StorageAssistantMapping;
typedef QList<IPersistenceAssistant* > NonNativeAssistants;

Persistence::~Persistence()
{
    foreach (const StorageNativeAssistantMapping& assistants,
        m_registeredNativeAssistants)
    {
        foreach (IPersistenceAssistant* assistant, assistants)
        {
            delete assistant;
        }
    }

    foreach (IStorage* storage, m_registeredStorages)
    {
        delete storage;
    }

    foreach (const StorageAssistantMapping& assistants,
        m_registeredAssistants)
    {
        foreach (const NonNativeAssistants& assistantList, assistants)
        {
            foreach (IPersistenceAssistant* assistant, assistantList)
            {
                delete assistant;
            }
        }
    }
}

void Persistence::initialize()
{
    if (persistence == 0)
    {
        persistence = new Persistence();
    }
}

void Persistence::dispose()
{
    delete persistence;
    persistence = 0;
}

void Persistence::registerStorage(const IStorage* storage)
{
    Q_ASSERT(persistence != 0);

    if (!persistence->m_registeredStorages.contains(
        storage->registeredName()))
    {
        persistence->m_registeredStorages[storage->registeredName()] =
            storage->copy();
    }
}

void Persistence::registerAssistant(const MimeType& mimeType,
    const IPersistenceAssistant* assistant)
{
    Q_ASSERT(persistence != 0);

    if (assistant->isNative(mimeType))
    {
        if (!persistence->m_registeredNativeAssistants.contains(mimeType))
        {
            if (!persistence->m_registeredNativeAssistants[mimeType].contains(
                assistant->forStorage()))
            {
                persistence->m_registeredNativeAssistants[mimeType][
                    assistant->forStorage()] =
                        assistant->copy();
            }
        }
    }
    else
    {
        persistence->m_registeredAssistants[mimeType][
            assistant->forStorage()].append(assistant->copy());
    }
}

QSharedPointer<IPersistenceAssistant> Persistence::getNativeAssistant(
    const QSharedPointer<IPersistentDocument>& document)
{
    Q_ASSERT(persistence != 0);

    QSharedPointer<IPersistenceAssistant> ret;

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        persistence->m_liveAssistants)
    {
        if (assistant->getPersistentDocument() == document &&
            assistant->isNative(document->mimeType()))
        {
            ret = assistant;
            break;
        }
    }

    if (ret.isNull())
    {
        const MimeType mimeType = document->mimeType();

        if (persistence->m_registeredNativeAssistants.contains(mimeType))
        {
            const QString storageName = document->storage()->name();

            if (persistence->m_registeredNativeAssistants[mimeType].contains(
                storageName))
            {
                QSharedPointer<IPersistenceAssistant> assistant(
                    persistence->m_registeredNativeAssistants[
                        mimeType][storageName]->copy());
                assistant->setPersistentDocument(document);

                persistence->m_liveAssistants.append(assistant);

                ret = assistant;
            }
        }
    }

    return ret;
}

QSharedPointer<IPersistenceAssistant> Persistence::getNativeAssistant(
    const QSharedPointer<IDocument>& document, const QString& storageName)
{
    Q_ASSERT(persistence != 0);

    QSharedPointer<IPersistenceAssistant> ret;

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        persistence->m_liveAssistants)
    {
        if (assistant->getDocument() == document &&
            assistant->isNative(document->mimeType()))
        {
            ret = assistant;
            break;
        }
    }

    if (ret.isNull())
    {
        const MimeType mimeType = document->mimeType();

        if (persistence->m_registeredNativeAssistants.contains(mimeType) &&
            persistence->m_registeredNativeAssistants[mimeType].contains(
                storageName))
        {
            QSharedPointer<IPersistenceAssistant> assistant(
                persistence->m_registeredNativeAssistants[
                    mimeType][storageName]->copy());
            assistant->setDocument(document);

            persistence->m_liveAssistants.append(assistant);

            ret = assistant;
        }
        else if (persistence->m_registeredNativeAssistants.contains(mimeType)
            && storageName == WILD_CARD)
        {
            const QString _storageName =
                persistence->m_registeredNativeAssistants[
                    mimeType].keys().first();

            QSharedPointer<IPersistenceAssistant> assistant(
                persistence->m_registeredNativeAssistants[
                    mimeType][_storageName]->copy());
            assistant->setDocument(document);

            persistence->m_liveAssistants.append(assistant);

            ret = assistant;
        }
    }

    return ret;
}

QList<QSharedPointer<IPersistenceAssistant> >
    Persistence::getNativeAssistants(
        const QSharedPointer<IDocument>& document)
{
    QList<QSharedPointer<IPersistenceAssistant> > ret;

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        persistence->m_liveAssistants)
    {
        if (assistant->getDocument() == document &&
            assistant->isNative(document->mimeType()))
        {
            ret.append(assistant);
            break;
        }
    }

    const MimeType mimeType = document->mimeType();

    if (persistence->m_registeredNativeAssistants.contains(mimeType))
    {
        foreach (IPersistenceAssistant* assistant,
            persistence->m_registeredNativeAssistants[mimeType])
        {
            QSharedPointer<IPersistenceAssistant> _assistant(
                assistant->copy());
            _assistant->setDocument(document);
            ret.append(_assistant);

            persistence->m_liveAssistants.append(_assistant);
        }
    }

    return ret;
}

QSharedPointer<IPersistenceAssistant> Persistence::getAssistant(
    const QSharedPointer<IPersistentDocument>& document,
    const MimeType& mimeType,
    uint operation)
{
    Q_ASSERT(persistence != 0 && operation != 0);

    QSharedPointer<IPersistenceAssistant> ret;

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        persistence->m_liveAssistants)
    {
        if (assistant->getPersistentDocument() == document)
        {
            uint openSave = OPEN | SAVE;
            if ((operation & openSave) == openSave)
            {
                if (assistant->canOpen(mimeType) &&
                    assistant->canSave(mimeType))
                {
                    ret = assistant;
                    break;
                }
            }
            else if ((operation & SAVE) == SAVE)
            {
                if (assistant->canSave(mimeType))
                {
                    ret = assistant;
                    break;
                }
            }
            else
            {
                if (assistant->canOpen(mimeType))
                {
                    ret = assistant;
                }
            }
        }
    }

    if (ret.isNull())
    {
        const MimeType mimeType = document->mimeType();

        if (persistence->m_registeredAssistants.contains(mimeType))
        {
            const QString storageName = document->storage()->name();

            if (persistence->m_registeredAssistants[mimeType].contains(
                storageName))
            {
                const QList<IPersistenceAssistant* > assistants =
                    persistence->m_registeredAssistants[mimeType][
                        storageName];

                foreach (IPersistenceAssistant* assistant, assistants)
                {
                    uint openSave = OPEN | SAVE;
                    if ((operation & openSave) == openSave)
                    {
                        if (assistant->canOpen(mimeType) &&
                            assistant->canSave(mimeType))
                        {
                            ret = QSharedPointer<IPersistenceAssistant>(
                                assistant->copy());
                        }
                    }
                    else if ((operation & SAVE) == SAVE)
                    {
                        if (assistant->canSave(mimeType))
                        {
                            ret = QSharedPointer<IPersistenceAssistant>(
                                assistant->copy());
                        }
                    }
                    else
                    {
                        if (assistant->canOpen(mimeType))
                        {
                            ret = QSharedPointer<IPersistenceAssistant>(
                                assistant->copy());
                        }
                    }
                }

                if (!ret.isNull())
                {
                    ret->setPersistentDocument(document);

                    persistence->m_liveAssistants.append(ret);
                }
            }
        }
    }

    return ret;
}

QSharedPointer<IPersistenceAssistant> Persistence::getAssistant(
    const QSharedPointer<IDocument>& document,
    const QString& storageName,
    const MimeType& mimeType,
    uint operation)
{
    Q_ASSERT(persistence != 0 && operation != 0);

    QSharedPointer<IPersistenceAssistant> ret;

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        persistence->m_liveAssistants)
    {
        if (assistant->getDocument() == document)
        {
            uint openSave = OPEN | SAVE;
            if ((operation & openSave) == openSave)
            {
                if (assistant->canOpen(mimeType) &&
                    assistant->canSave(mimeType))
                {
                    ret = assistant;
                    break;
                }
            }
            else if ((operation & SAVE) == SAVE)
            {
                if (assistant->canSave(mimeType))
                {
                    ret = assistant;
                    break;
                }
            }
            else
            {
                if (assistant->canOpen(mimeType))
                {
                    ret = assistant;
                }
            }
        }
    }

    if (ret.isNull())
    {
        const MimeType mimeType = document->mimeType();

        if (persistence->m_registeredAssistants.contains(mimeType))
        {
            if (persistence->m_registeredAssistants[mimeType].contains(
                storageName))
            {
                const QList<IPersistenceAssistant* > assistants =
                    persistence->m_registeredAssistants[mimeType][
                        storageName];

                foreach (IPersistenceAssistant* assistant, assistants)
                {
                    uint openSave = OPEN | SAVE;
                    if ((operation & openSave) == openSave)
                    {
                        if (assistant->canOpen(mimeType) &&
                            assistant->canSave(mimeType))
                        {
                            ret = QSharedPointer<IPersistenceAssistant>(
                                assistant->copy());
                        }
                    }
                    else if ((operation & SAVE) == SAVE)
                    {
                        if (assistant->canSave(mimeType))
                        {
                            ret = QSharedPointer<IPersistenceAssistant>(
                                assistant->copy());
                        }
                    }
                    else
                    {
                        if (assistant->canOpen(mimeType))
                        {
                            ret = QSharedPointer<IPersistenceAssistant>(
                                assistant->copy());
                        }
                    }
                }

                if (!ret.isNull())
                {
                    ret->setDocument(document);

                    persistence->m_liveAssistants.append(ret);
                }
            }
        }
    }

    return ret;
}

void Persistence::closeAssistant(
    const QSharedPointer<IPersistenceAssistant>& assistant)
{
    Q_ASSERT(persistence != 0);

    foreach (const QSharedPointer<IPersistenceAssistant>& _assistant,
        persistence->m_liveAssistants)
    {
        if (_assistant == assistant)
        {
            Q_ASSERT(assistant->getPersistentDocument().isNull());

            persistence->m_liveAssistants.removeAll(assistant);
            break;
        }
    }
}

void Persistence::closeAssistants(
    const QSharedPointer<IDocument>& document)
{
    Q_ASSERT(persistence != 0);

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        persistence->m_liveAssistants)
    {
        if (assistant->getDocument() == document)
        {
            persistence->m_liveAssistants.removeAll(assistant);
        }
    }
}

uint Persistence::liveAssistants(
    const QSharedPointer<IDocument>& document)
{
    Q_ASSERT(persistence != 0);

    uint ret = 0;

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        persistence->m_liveAssistants)
    {
        if (assistant->getDocument() == document)
        {
            ++ret;
        }
    }

    return ret;
}

uint Persistence::liveAssistants()
{
    Q_ASSERT(persistence != 0);

    uint ret = persistence->m_liveAssistants.size();

    return ret;
}

QSharedPointer<IDocument> Persistence::open(
    const QSharedPointer<IPersistentDocument>& document)
{
    Q_ASSERT(persistence != 0);

    const QSharedPointer<IPersistenceAssistant> assistant =
        getNativeAssistant(document);

    Q_ASSERT(!assistant.isNull()); // assistant not registered?

    return assistant->open();
}

QSharedPointer<IDocument> Persistence::openAs(
    const QSharedPointer<IPersistentDocument>& document,
    const MimeType& mimeType)
{
    Q_ASSERT(persistence != 0);

    const QSharedPointer<IPersistenceAssistant> assistant =
        getAssistant(document, mimeType, OPEN);

    Q_ASSERT(!assistant.isNull()); // assistant not registered?

    return assistant->open();
}

QSharedPointer<IPersistentDocument> Persistence::save(
    const QSharedPointer<IDocument>& document,
    const QString& storageName, bool& canceled)
{
    Q_ASSERT(persistence != 0);

    QSharedPointer<IPersistentDocument> ret;

    const QSharedPointer<IPersistenceAssistant> assistant =
        getNativeAssistant(document, storageName);

    Q_ASSERT(!assistant.isNull()); // assistant not registered?

    bool canSave = true;

    if (assistant->getPersistentDocument().isNull())
    {
        Q_ASSERT(persistence->m_registeredStorages.contains(
            assistant->forStorage()));

        IStorage* storage = persistence->m_registeredStorages[
            assistant->forStorage()];

        QUrl persistentDocumentUrl = storage->urlForSave(document->name(),
            document->mimeType(), canceled);

        if (persistentDocumentUrl.isValid() &&
            (!persistentDocumentUrl.authority().isEmpty() ||
                !persistentDocumentUrl.path().isEmpty()))
        {
            assistant->setPersistentDocument(
                createDocument(persistentDocumentUrl, storageName));
        }
        else
        {
            canSave = false;
        }
    }

    if (canSave)
    {
        ret = assistant->save();
    }

    return ret;
}

QSharedPointer<IPersistentDocument> Persistence::saveAs(
    const QSharedPointer<IDocument>& document,
    const QString& storageName,
    const MimeType& mimeType)
{
    Q_ASSERT(persistence != 0);

    const QSharedPointer<IPersistenceAssistant> assistant =
        getAssistant(document, storageName, mimeType, SAVE);

    Q_ASSERT(!assistant.isNull()); // assistant not registered?

    return assistant->save();
}

QSharedPointer<IPersistentDocument> Persistence::createDocument(
    const QUrl& url, const QString& storageName)
{
    Q_ASSERT(persistence != 0 &&
        persistence->m_registeredStorages.contains(storageName));

    QSharedPointer<IPersistentDocument> ret;

    IStorage* storage = persistence->m_registeredStorages[storageName];

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        persistence->m_liveAssistants)
    {
        if (!assistant.isNull())
        {
            QSharedPointer<IPersistentDocument> document =
                assistant->getPersistentDocument();

            if (!document.isNull() && document->url() == url &&
                *document->storage() == *storage)
            {
                ret = document;
                break;
            }
        }
    }

    if (ret.isNull())
    {
        bool status = storage->create(url);

        const MimeType mimeType =
            MimeTypeManager::getInstance()->mimeTypeForUrl(url);

        if (status && persistence->m_registeredNativeAssistants.contains(
            mimeType))
        {
            QSharedPointer<IPersistenceAssistant> assistant(
                persistence->m_registeredNativeAssistants[mimeType][
                    storageName]->copy());
            persistence->m_liveAssistants.append(assistant);

            status = assistant->createPersistentDocument(url, storage);

            if (status)
            {
                ret = assistant->getPersistentDocument();
            }
        }
    }

    return ret;
}

bool Persistence::deleteDocument(QSharedPointer<IPersistentDocument>&
    document)
{
    Q_ASSERT(persistence != 0);

    bool ret = false;

    ret = document->storage()->erase(document);

    QList<QSharedPointer<IPersistenceAssistant> > assistantsToRemove;

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        persistence->m_liveAssistants)
    {
        if (assistant->getPersistentDocument() == document)
        {
            assistantsToRemove.append(assistant);
        }
    }

    foreach (const QSharedPointer<IPersistenceAssistant>& assistant,
        assistantsToRemove)
    {
        persistence->m_liveAssistants.removeAll(assistant);
    }

    return ret;
}

IStorage* Persistence::getStorage(const QString& storageName)
{
    Q_ASSERT(persistence != 0);

    IStorage* ret = 0;

    if (persistence->m_registeredStorages.contains(storageName))
    {
        ret = persistence->m_registeredStorages[storageName];
    }

    return ret;
}

QHash<QString, QString> Persistence::registeredStorages()
{
    Q_ASSERT(persistence != 0);

    QHash<QString, QString> ret;

    foreach (const IStorage* storage, persistence->m_registeredStorages)
    {
        ret[storage->name()] = storage->registeredName();
    }

    return ret;
}

Persistence::Persistence() :
    m_registeredAssistants(),
    m_registeredNativeAssistants(),
    m_registeredStorages(),
    m_liveAssistants()
{

}
